#include <torrent/listen.h>
#include "peer_handshake.h"
#include "torrent.h"

#include "exceptions.h"
#include "download.h"
#include "peer_connection.h"
#include "config.h"

#include <algorithm>
#include <cassert>
#include <cstdlib>
#include <iostream>
#include <iosfwd>

namespace torrent {

int64_t Timer::m_cache;
std::list<std::string> caughtExceptions;

struct add_socket {
	add_socket(fd_set* s) : fd(0), fds(s) {}

	void operator() (SocketBase* s) {
		if (s->fd() < 0)
			throw internal_error("Tried to poll a negative file descriptor");

		if (fd < s->fd())
			fd = s->fd();

		FD_SET(s->fd(), fds);
	}

	int fd;
	fd_set* fds;
};

struct check_socket_isset {
	check_socket_isset(fd_set* s) : fds(s) {}

	bool operator() (SocketBase* socket) {
		assert(socket != NULL);

		return FD_ISSET(socket->fd(), fds);
	}

	fd_set* fds;
};

void shutdown() {
	Listen::close();

	for (std::list<Download*>::iterator it = Download::downloads().begin();
			it != Download::downloads().end(); ++it) {
		(*it)->stop();
	}
}

int mark(fd_set* readSet, fd_set* writeSet, fd_set* exceptSet) {
	int maxFd = 0;

	maxFd = std::max(maxFd, std::for_each(SocketBase::readSockets().begin(), SocketBase::readSockets().end(),
			add_socket(readSet)).fd);
	maxFd = std::max(maxFd, std::for_each(SocketBase::writeSockets().begin(), SocketBase::writeSockets().end(),
			add_socket(writeSet)).fd);
	maxFd = std::max(maxFd, std::for_each(SocketBase::exceptSockets().begin(), SocketBase::exceptSockets().end(),
			add_socket(exceptSet)).fd);

	return maxFd;
}

// Do work on the file descriptors.
void work(fd_set* readSet, fd_set* writeSet, fd_set* exceptSet) {
	// Update the cached time.
	Timer::update();

	if (readSet == NULL || writeSet == NULL || exceptSet == NULL)
		throw internal_error("Torrent::work received a NULL pointer to a fs_set");

	// Make sure we don't do read/write on fd's that are in except. This should
	// not be a problem as any except call should remove it from the m_*Set's.

	caughtExceptions.clear();

	// If except is called, make sure you correctly remove us from the poll.

}

void initialize(int beginPort, int endPort) {
	srandom(Timer::current().usec());

	Listen::open(beginPort, endPort);
}

DList::const_iterator create(std::istream& s) {
	// Clear failed bits
	s.clear();

	bencode b;

	s >> b;

	if (s.fail())
		// Make it configurable whetever we throw or return .end()?
		throw local_error("Could not parse bencode torrent");


	Download* download = new Download(b);
	return Download::downloads().insert(Download::downloads().end(), download);
}

// List container with all the current downloads.
const DList& downloads() {
	return Download::downloads();
}

// List of all connected peers in 'd'
const PList& peers(DList::const_iterator d) {
	return (*d)->connections();
}

bool start(DList::const_iterator d) {
	(*d)->start();
	return true;
}

// Throws a local_error of some sort.
// TODO
int64_t get(GValue t) {
	switch(t) {
	case LISTEN_PORT:
		return Listen::port();

	case HANDSHAKE_TOTAL:
		return PeerHandshake::handshakes().size();

	case TIME_CURRENT:
		return Timer::current().usec();

	case TIME_SELECT:
		return Service::nextService().usec();

	default:
		throw internal_error("get(GValue) received invalid type");
	}
}

std::string get(GString t) {
	std::string s;

	switch (t) {
		case LIBRARY_NAME:
			return std::string("LibTorrent") + " " VERSION;

		default:
			throw internal_error("get(GString) received invalid type");
	}
}


// TODO
int64_t get(DList::const_iterator d, DValue t) {
//		if (d == Download::downloads().end())
//			throw internal_error("torrent::get(DList::const_iterator, DValue) called on an invalid iterator");
	Timer::update();
	uint64_t a;

	switch (t) {
		case BYTES_DOWNLOADED:
			return (*d)->bytesDownloaded();

		case BYTES_UPLOADED:
			return (*d)->bytesUploaded();

		case BYTES_TOTAL:
			return (*d)->files().totalSize();

		case BYTES_DONE:
			a = 0;
			// TODO
			for (Delegator::Chunks::iterator it = (*d)->delegator().chunks().begin();
					it != (*d)->delegator().chunks().end(); ++it) {
				for (std::list<Delegator::PieceInfo>::iterator itr = it->m_pieces.begin();
						itr != it->m_pieces.end(); ++itr) {
					a = a + itr->m_piece.length();
				}
			}
			return a + (*d)->files().doneSize();

		case CHUNKS_DONE:
			return (*d)->files().chunkCompleted();

		case CHUNKS_SIZE:
			return (*d)->files().chunkSize();

		case CHUNKS_TOTAL:
			return (*d)->files().chunkCount();

		case RATE_UP:
			return (*d)->rateUp().rate(true);

		case RATE_DOWN:
			return (*d)->rateDown().rate(true);

		case PEERS_MIN:
			return (*d)->settings().minPeers;

		case PEERS_MAX:
			return (*d)->settings().maxPeers;

		case PEERS_CONNECTED:
			return (*d)->connections().size();

		case PEERS_NOT_CONNECTED:
			return (*d)->availablePeers().size();

		case TRACKER_CONNECTING:
			return (*d)->tracker().busy();

		case TRACKER_TIMEOUT:
			return (*d)->tracker().inService(0) ? ((*d)->tracker().whenService(0) - Timer::current()).usec() : 0;

		case UPLOADS_MAX:
			return (*d)->settings().maxUploads;

		default:
			throw internal_error("get(itr, DValue) received invalid type");
	}
}

int64_t get(PList::const_iterator p, PValue t) {
	Timer::update();

	switch (t) {
		case PEER_LOCAL_CHOKED:
			return (*p)->up().c_choked();

		case PEER_LOCAL_INTERESTED:
			return (*p)->up().c_interested();

		case PEER_REMOTE_CHOKED:
			return (*p)->down().c_choked();

		case PEER_REMOTE_INTERESTED:
			return (*p)->down().c_interested();

		case PEER_CHOKE_DELAYED:
			return (*p)->chokeDelayed();

		case PEER_RATE_DOWN:
			return (*p)->down().c_rate().rate(true);

		case PEER_RATE_UP:
			return (*p)->up().c_rate().rate(true);

		case PEER_PORT:
			return (*p)->peer().port();

		default:
			throw internal_error("get(itr, PValue) received invalid type");
	}
}

//TODO
std::string get(DList::const_iterator d, DString t) {
	std::string s;

	switch (t) {
	case BITFIELD_LOCAL:
		return std::string((*d)->files().bitfield().data(), (*d)->files().bitfield().sizeBytes());

	case BITFIELD_SEEN:
		for (BitFieldCounter::Field::const_iterator it = (*d)->delegator().bfCounter().field().begin();
				it != (*d)->delegator().bfCounter().field().end(); ++it) {
			if ((*it) < 256) {
				s.push_back((char)*it);
			} else {
				s.push_back((char)255);
			}
		}
		return s;

	case INFO_NAME:
		return (*d)->name();

	case TRACKER_MSG:
		return (*d)->tracker().msg();
	}
}

// TODO
std::string get(PList::const_iterator p, PString t) {
	std::string s;

	switch (t) {
		case PEER_ID:
			return (*p)->peer().id();

		case PEER_DNS:
			return (*p)->peer().dns();

		case PEER_BITFIELD:
			return std::string((*p)->bitfield().data(), (*p)->bitfield().sizeBytes());

		case PEER_INCOMING:
			for (Sub::PieceList::iterator it = (*p)->down().c_list().begin(); it != (*p)->down().c_list().end(); ++it) {
				s += std::string((char*)&(it->index()), 4);
			}
			return s;

		case PEER_OUTGOING:
			for (Sub::PieceList::iterator it = (*p)->up().c_list().begin(); it != (*p)->up().c_list().end(); ++it) {
				s += std::string((char*)&(it->index()), 4);
			}
			return s;

		default:
			throw internal_error("get(itr, PString) received invalid type");
	}
}

}
